﻿using System.Data;
using AutoMapper;
using Benefactor.Jobs;
using Benefactor.Models.DbModels;
using Benefactor.Models.DtoModels;
using Benefactor.Services;
using Benefactor.Services.Repositories;
using Bigway.Common;
using Bigway.Common.Jobs;
using Bigway.Common.Models.DtoModels;
using Bigway.Common.Services.Repositories;
using Microsoft.AspNetCore.Mvc;
using SqlSugar;

namespace Benefactor.Controllers;
using static Errors;

[ApiController]
[Route("api/benefits_card")]
#pragma warning disable S6960
public class BenefitsCardController(
#pragma warning restore S6960
    IBenefitsCardService benefitsCardService,
    IBenefitsCardRepository benefitsCardRepository,
    ICrmebProductRepository crmebProductRepository,
    IMapper mapper,
    IAppConfig appConfig,
    ILogger<BenefitsCardController> logger): ControllerBase
{
    [HttpPost]
    public async Task<AddBenefitsCardResponseDto> AddBenefitsCardAsync(
        [FromServices]ISoftwareSystemRepository softwareSystemRepository,
        [FromQuery(Name = "api_key")] string apiKey,
        [FromBody] AddBenefitsCardRequestDto request,
        CancellationToken token = default
    )
    {
        logger.LogInformation("Adding benefits card for system {SystemId}", request.SystemId);
        if (apiKey != appConfig.ApiKey)
            return Unauthorized<AddBenefitsCardResponseDto>();

        var system = await softwareSystemRepository.GetAsync(request.SystemId, token);
        if (system is null)
            return SystemNotFound<AddBenefitsCardResponseDto>(request.SystemId);

        var crmebProducts = await crmebProductRepository.GetAllBenefitsCardStoreProducts(token);
        var crmebProductId = request.MerchantProductId;
        CrmebProduct? crmebProduct = null;
        if (crmebProductId is not null)
        {
            crmebProduct = crmebProducts.Find(x => x.Id == int.Parse(crmebProductId));
            if (crmebProduct is null)
            {
                return CrmebProductNotFound<AddBenefitsCardResponseDto>(crmebProductId);
            }
        }

        var benefitsCard = await benefitsCardRepository.AddAsync(
            mapper.Map<BenefitsCard>(request), token);

        var benefitsCardInfo = mapper.Map<BenefitsCardInfo>(benefitsCard);
        var attrValues = crmebProduct?.AttrValues;
        if (attrValues is not null)
        {
            benefitsCardInfo.CrmebMerchantProductAttrValues = attrValues
                .Select(x => x.Id);
        }
        return new AddBenefitsCardResponseDto
        {
            Data = benefitsCardInfo
        };
    }

    [HttpPut]
    public async Task<NoDataResponseDto> UpdateBenefitsCardAsync(
        [FromQuery(Name = "api_key")] string apiKey,
        [FromBody] UpdateBenefitsCardRequestDto request,
        CancellationToken token = default
    )
    {
        var benefitsCardId = request.Id;
        logger.LogInformation("Updating benefits card of id {BenefitsCardId}", benefitsCardId);
        if (apiKey != appConfig.ApiKey)
            return Unauthorized<NoDataResponseDto>();

        var benefitsCard = await benefitsCardRepository.GetByIdAsync(
            benefitsCardId, token);

        if (benefitsCard is null)
            return BenefitsCardNotFound<NoDataResponseDto>(benefitsCardId);
        else if (benefitsCard.IsDeleted)
            return BenefitsCardDeleted<NoDataResponseDto>(benefitsCardId);

        var crmebProducts = await crmebProductRepository.GetAllBenefitsCardStoreProducts(token);
        var crmebProductId = request.MerchantProductId;
        CrmebProduct? crmebProduct;
        if (crmebProductId is not null)
        {
            crmebProduct = crmebProducts.Find(x => x.Id.ToString() == crmebProductId);
            if (crmebProduct is null)
            {
                return CrmebProductNotFound<NoDataResponseDto>(crmebProductId);
            }
        }

        benefitsCard = mapper.Map(request, benefitsCard);

        await benefitsCardRepository.UpdateAsync(benefitsCard, token);

        return new NoDataResponseDto();
    }

    [HttpDelete]
    public async Task<NoDataResponseDto> DeleteBenefitsCardAsync(
        [FromQuery(Name = "api_key")] string apiKey,
        [FromBody] DeleteEntityRequestDto request,
        CancellationToken token = default
    )
    {
        var benefitsCardId = request.Id;
        logger.LogInformation("Deleting benefits card of id {BenefitsCardId}",
            benefitsCardId);
        if (apiKey != appConfig.ApiKey)
            return Unauthorized<NoDataResponseDto>();

        var benefitsCard = await benefitsCardRepository.GetByIdAsync(benefitsCardId, token);
        if (benefitsCard is null)
            return BenefitsCardNotFound<NoDataResponseDto>(benefitsCardId);
        else if (benefitsCard.IsDeleted)
            return BenefitsCardDeleted<NoDataResponseDto>(benefitsCardId);

        benefitsCard.IsDeleted = true;

        await benefitsCardRepository.UpdateAsync(benefitsCard, token);

        return new NoDataResponseDto();
    }

    [HttpGet]
    public async Task<GetBenefitsCardsResponseDto> GetBenefitsCardsAsync(
        [FromQuery(Name = "api_key")] string apiKey,
        [FromQuery] GetBenefitsCardRequestDto request,
        CancellationToken token = default
    )
    {
        if (apiKey != appConfig.ApiKey)
            return Unauthorized<GetBenefitsCardsResponseDto>();

        var systemId = request.system_id;
        var districtIds = request.district_ids;
        var benefitsCardId = request.benefits_card_id;
        var includeBenefits = request.include_benefits ?? false;
        logger.LogInformation("Getting benefits cards for system {SystemId}",
            systemId);

        var benefitsCards = await benefitsCardRepository.GetAllAsync(
            query =>
            {
                query = query.Where(x => !x.IsDeleted && x.SystemId == systemId);
                if (districtIds is { Length: > 0})
                    query = query.Where(x => districtIds.Contains(x.DistrictId));
                if (!string.IsNullOrEmpty(benefitsCardId))
                    query = query.Where(x => x.Id == benefitsCardId);

                if (includeBenefits)
                {
                    query = query.Includes(x => x.Benefits, x => x.Product);
                }

                query = query.OrderByDescending(x => x.CreateTime);

                return query;
            }, token);

        if (benefitsCards.Count == 0)
            return new GetBenefitsCardsResponseDto();

        var merchantProductIds = benefitsCards
            .Select(x => x.MerchantProductId)
            .Distinct()
            .Where(x => !string.IsNullOrEmpty(x))
            .Select(x => int.Parse(x!))
            .ToList();

        var crmebProducts = await crmebProductRepository.GetAllBenefitsCardStoreProducts(merchantProductIds, token);
        var crmebProductsMap = crmebProducts.ToDictionary(x => x.Id.ToString());

        var benefitsCardInfos = benefitsCards
            .Select(benefitsCard =>
            {
                var benefitsCardInfo = mapper.Map<BenefitsCardInfo>(benefitsCard);
                var merchantProductId = benefitsCardInfo.MerchantProductId;
                if (merchantProductId is null)
                    return benefitsCardInfo;
                var crmebProduct = crmebProductsMap.GetValueOrDefault(merchantProductId);
                var attrValues = crmebProduct?.AttrValues;
                benefitsCardInfo.CrmebMerchantProductAttrValues = attrValues is not null
                    ? attrValues.Select(x => x.Id)
                    : [];

                return benefitsCardInfo;
            });

        return new GetBenefitsCardsResponseDto
        {
            Data = benefitsCardInfos
        };
    }

    [HttpPost("issuethenaddorder")]
    public async Task<IssueBenefitsCardThenAddOrderResponseDto> IssueBenefitsCardThenAddOrderAsync(
        [FromServices] IServiceProvider serviceProvider,
        [FromServices] JobRegistryProvider jobRegistryProvider,
        [FromServices] ISoftwareSystemRepository softwareSystemRepository,
        [FromServices] IBenefitsCardOrderRepository benefitsCardOrderRepository,
        [FromServices] ICrmebOrderRepository crmebOrderRepository,
        [FromServices] IIssuedBenefitsCardRepository issuedBenefitsCardRepository,
        [FromQuery(Name = "api_key")] string apiKey,
        [FromBody] IssueBenefitsCardThenAddOrderRequestDto request,
        CancellationToken token = default
    )
    {
        var systemId = request.SystemId;
        var orderNo = request.OrderNo;
        logger.LogInformation("Issuing benefits card for system {SystemId} and benefits card with id {BenefitsCardId} and then add order for {OrderNo}",
            request.SystemId,
            request.BenefitsCardId,
            request.OrderNo);
        if (apiKey != appConfig.ApiKey)
            return Unauthorized<IssueBenefitsCardThenAddOrderResponseDto>();

        var system = await softwareSystemRepository.GetAsync(request.SystemId, token);
        if (system is null)
            return SystemNotFound<IssueBenefitsCardThenAddOrderResponseDto>(request.SystemId);

        var benefitsCardId = request.BenefitsCardId;
        var benefitsCard = await benefitsCardRepository.GetByIdAsync(
            benefitsCardId,
            token
        );

        if (benefitsCard is null)
            return BenefitsCardNotFound<IssueBenefitsCardThenAddOrderResponseDto>(benefitsCardId);

        if (benefitsCard.IsDeleted)
            return BenefitsCardDeleted<IssueBenefitsCardThenAddOrderResponseDto>(benefitsCardId);

        var benefitsCardOrders = await benefitsCardOrderRepository.GetAllAsync(query =>
                query.Where(x => x.SystemId == systemId && x.OrderNo == orderNo),
            token);

        if (benefitsCardOrders.Exists(x => x.OrderNo == orderNo))
        {
            return OrderNoAlreadyLinked<IssueBenefitsCardThenAddOrderResponseDto>(orderNo);
        }

        var crmebOrders = await crmebOrderRepository.GetAllAsync(query =>
            query.Where(x => x.OrderNo == orderNo || x.PlatformOrderNo == orderNo), token);

        if (crmebOrders.Count == 0)
        {
            return CrmebOrderNotFound<IssueBenefitsCardThenAddOrderResponseDto>(orderNo);
        }

        var issuedBenefitsCard = await benefitsCardService.IssueAsync(
            benefitsCard,
            request.UserId,
            request.ValidFrom,
            request.ValidTo,
            token
        );

        var benefitsCardOrder = mapper.Map<BenefitsCardOrder>(request);
        benefitsCardOrder.IssuedBenefitsCardId = issuedBenefitsCard.Id;
        benefitsCardOrder.IssuedBenefitsCardNo = issuedBenefitsCard.CardNo;
        benefitsCardOrder.BenefitsCardOrderStatusCode = Statics.PaidBenefitsCardOrderStatus;

        var sqliteDb = serviceProvider.GetRequiredKeyedService<ISqlSugarClient>(IocKeys.SqliteDbKey);
        var ado = sqliteDb.Ado;

        await ado.BeginTranAsync(IsolationLevel.ReadCommitted);
        try
        {
            await issuedBenefitsCardRepository.AddAsync(issuedBenefitsCard, token);
            await benefitsCardOrderRepository.AddAsync(benefitsCardOrder, token);
            await ado.CommitTranAsync();
        }
        catch (Exception ex)
        {
            logger.LogError(ex,
                "Unable to issue and add benefits card order for benefits card {BenefitsCardId} and user {UserId}",
                request.BenefitsCardId,
                request.UserId);
            await ado.RollbackTranAsync();
            return InternalError<IssueBenefitsCardThenAddOrderResponseDto>(ex);
        }

        var jobRegistry = jobRegistryProvider.JobRegisty;
        var generateVouchersJobInfo = jobRegistry.GetJobInfo<GenerateVouchersJob>(SystemIds.GiftPiker)!;

        _ = Task.Run(async () =>
        {
            // ReSharper disable once MethodSupportsCancellation
            await generateVouchersJobInfo.ScheduleToRunImmediatelyWithDefaultDelayAsync();
        }, token);

        var orderProcessJob = jobRegistry.GetJobInfo(SystemIds.GiftPiker, nameof(BenefitsCardOrderProcessJob))!;
        _ = Task.Run(async () =>
        {
            // ReSharper disable once MethodSupportsCancellation
            await orderProcessJob.ScheduleToRunImmediatelyWithDefaultDelayAsync();
        }, token);

        var issuedBenefitsCardInfo = mapper.Map<IssuedBenefitsCardInfo>(issuedBenefitsCard);
        var benefitsCardOrderInfo = mapper.Map<BenefitsCardOrderInfo>(benefitsCardOrder);
        return new IssueBenefitsCardThenAddOrderResponseDto
        {
            Data = new IssueBenefitsCardThenAddOrderData()
            {
                IssuedBenefitsCard = issuedBenefitsCardInfo,
                BenefitsCardOrder = benefitsCardOrderInfo
            }
        };
    }
}